#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>

#include "log.h"
#include "scull_common.h"

#define SCULL_NAME "sculluid"

char *scull_name = SCULL_NAME;

static uid_t scull_u_uid = 0;
static uint32_t scull_u_count = 0;
DEFINE_SPINLOCK(scull_u_lock);

struct scull_dev *uiddev = NULL;

static int scull_u_open(struct inode *inode, struct file *filp) {
    LOGI("%s enter\n", __FUNCTION__);
    int res = 0;

    spin_lock(&scull_u_lock);
    if (scull_u_count == 0) {
        scull_u_uid = current_uid().val;
    }
    if (scull_u_uid != current_uid().val        /* allow same user */
        && scull_u_uid != current_euid().val    /* allow su switched user */
        && !capable(CAP_DAC_OVERRIDE)) {        /* allow root */
        res = -EBUSY;
        goto unlock;
    }

    scull_u_count++;
    res = scull_open(inode, filp);

unlock:
    LOGI("scull_u_open, uid %u, count %u, res %d",
            scull_u_uid, scull_u_count, res);
    spin_unlock(&scull_u_lock);
    LOGI("%s exit\n", __FUNCTION__);
    return res;
}

static int scull_u_release(struct inode *inode, struct file *filp) {
    LOGI("%s enter\n", __FUNCTION__);
    spin_lock(&scull_u_lock);
    if (scull_u_count > 0) {
        scull_u_count--;
    } else {
        LOGI("scull_u_release, count already 0");
        scull_u_uid = 0;
    }
    LOGI("scull_u_release, uid %u, count %u",
            scull_u_uid, scull_u_count);
    spin_unlock(&scull_u_lock);
    LOGI("%s exit\n", __FUNCTION__);
    return 0;
}

const struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .open = scull_u_open,
    .read = scull_read,
    .write = scull_write,
    .llseek = scull_llseek,
    .release = scull_u_release,
};

static int scull_u_init(void) {
    LOGI("%s enter\n", __FUNCTION__);
    int res = 0;

    scull_dev_nr = 1;
    uiddev = kzalloc(sizeof(struct scull_dev), GFP_KERNEL);
    if (uiddev == NULL) {
        LOGE("alloc scull_dev failed");
        res = -ENOMEM;
        goto out;
    }
    res = scull_setup_cdev(uiddev, 0, &scull_fops);
    if (res) {
        kfree(uiddev);
        uiddev = NULL;
        return res;
    }
    LOGI("register chrdev region, major %d, minor %d, count %d",
            scull_major, scull_minor, scull_dev_nr);

out:
    LOGI("%s exit\n", __FUNCTION__);
    return res;
}
module_init(scull_u_init);

static void scull_u_exit(void) {
    LOGI("%s enter\n", __FUNCTION__);
    if (uiddev) {
        scull_delete_cdev(uiddev, 0);
        kfree(uiddev);
        uiddev = NULL;
    }
    unregister_chrdev_region(MKDEV(scull_major, scull_minor), scull_dev_nr);
    scull_u_uid = 0;
    LOGI("%s exit\n", __FUNCTION__);
    return;
}
module_exit(scull_u_exit);

MODULE_LICENSE("GPL");